#include "njm_pipeline.hpp"

#include <fstream>
#include <sstream>
#include <stdexcept>
#include <iostream>
#include <filesystem>
#include <cassert>
#include "njm_model.hpp"

namespace njm {

    NjmPipeline::~NjmPipeline()
    {
        vkDestroyShaderModule(njmDevice.device(), vertShaderModule, nullptr);
        vkDestroyShaderModule(njmDevice.device(), fragShaderModule, nullptr);
        vkDestroyPipeline(njmDevice.device(), graphicsPipeline, nullptr);
    }

    NjmPipeline::NjmPipeline(
            NjmDevice& device,
            const std::string& vertPath, 
            const std::string& fragPath,
            const PipelineConfigInfo& configInfo): njmDevice(device)
    {
        createGraphicsPipeline(vertPath, fragPath, configInfo);
    }

    std::vector<char> NjmPipeline::readFile(const std::string& filePath)
    {
        namespace fs = std::filesystem;
        auto cwd = fs::current_path().lexically_normal();
        auto myPath = fs::path(filePath).lexically_normal();
        auto absolutePath = cwd / myPath;
        std::cout << "cwd: " << cwd << std::endl;
        std::cout << "path:" << myPath << std::endl;
        std::cout << "absolutePath:" << absolutePath << std::endl;

        std::string code;
        const char* code_c;
        std::ifstream file;
        std::vector<char> buffer;
        file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            file.open(absolutePath, std::ios::ate | std::ios::binary);
            size_t fileSize = static_cast<size_t>(file.tellg());
            std::cout << "filesize:" << fileSize << std::endl;
            buffer.resize(fileSize);
            file.seekg(0);
            file.read(buffer.data(), fileSize);

            // file.open(absolutePath);
            // std::stringstream codestream;
            // codestream << file.rdbuf();
            // code = codestream.str();
            // code_c = code.c_str();

            file.close();
        }
        catch(std::ifstream::failure e)
        {
            std::cerr << "ERROR::PIPELINE::FILE_READ_FAIL: " << e.what() << std::endl;
        }
        return buffer;

        // std::fstream file{filePath, std::ios::ate | std::ios::binary};
        // if (!file.is_open())
        // {
        //     std::cerr << "ERROR::PIPELINE::FILE_READ_FAIL: " << filePath << std::endl;
        //     throw std::runtime_error("failed to open file: " + filePath);
        // }
        // size_t fileSize = static_cast<size_t>(file.tellg());
        // std::vector<char> buffer(fileSize);
        // file.seekg(0);
        // file.read(buffer.data(), fileSize);
        // file.close();
        // return buffer;
    }

    void NjmPipeline::createGraphicsPipeline(
            const std::string& vertPath, 
            const std::string& fragPath,
            const PipelineConfigInfo& configInfo)
    {
        assert(configInfo.pipelineLayout != VK_NULL_HANDLE && "[NjmPipeline::createGraphicsPipeline] : PipelineConfigInfo has no layout");
        assert(configInfo.renderPass != VK_NULL_HANDLE && "[NjmPipeline::createGraphicsPipeline] : PipelineConfigInfo has no renderpass");

        auto vertCode = readFile(vertPath);
        auto fragCode = readFile(fragPath);

        createShaderModule(vertCode, &vertShaderModule); 
        createShaderModule(fragCode, &fragShaderModule);       

        VkPipelineShaderStageCreateInfo shaderStages[2];
        shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
        shaderStages[0].module = vertShaderModule;
        shaderStages[0].pName = "main";
        shaderStages[0].pNext = nullptr;
        shaderStages[0].flags = 0;
        shaderStages[0].pSpecializationInfo = nullptr;
        shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
        shaderStages[1].module = fragShaderModule;
        shaderStages[1].pName = "main";
        shaderStages[1].pNext = nullptr;
        shaderStages[1].flags = 0;
        shaderStages[1].pSpecializationInfo = nullptr;

        auto& bindingDescriptions = configInfo.bindingDescriptions;
        auto& attributeDescriptions = configInfo.attributeDescriptions;
        VkPipelineVertexInputStateCreateInfo vertInputInfo{};
        vertInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
        vertInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescriptions.size());
        vertInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
        vertInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindingDescriptions.size());
        vertInputInfo.pVertexBindingDescriptions = bindingDescriptions.data();

        VkGraphicsPipelineCreateInfo pipelineInfo{};
        pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
        pipelineInfo.stageCount = 2;
        pipelineInfo.pStages = shaderStages;
        pipelineInfo.pVertexInputState = &vertInputInfo;

        pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
        pipelineInfo.pViewportState = &configInfo.viewportInfo;
        pipelineInfo.pRasterizationState = &configInfo.rasInfo;
        pipelineInfo.pMultisampleState = &configInfo.msInfo;
        pipelineInfo.pColorBlendState = &configInfo.colorblendInfo;
        pipelineInfo.pDepthStencilState = &configInfo.depthstencilInfo;
        pipelineInfo.pDynamicState = &configInfo.dynamicStateInfo;

        pipelineInfo.layout = configInfo.pipelineLayout;
        pipelineInfo.renderPass = configInfo.renderPass;
        pipelineInfo.subpass = configInfo.subpass;

        pipelineInfo.basePipelineIndex = -1;
        pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;

        if (vkCreateGraphicsPipelines(
                njmDevice.device(), 
                VK_NULL_HANDLE, 1, &pipelineInfo, 
                nullptr, &graphicsPipeline) != VK_SUCCESS)
        {
            std::cerr << "ERROR::PIPELINE::createGraphicsPipeline : failed to create graphics-pipeline" << std::endl;
            throw std::runtime_error("failed to create graphics-pipeline");
        }       
    }

    // void NjmPipeline::createComputePipeline(
    //         const std::string& computePath, 
    //         const PipelineConfigInfo& configInfo)
    // {
    //     auto vertCode = readFile(computePath);
    // }

    void NjmPipeline::createShaderModule(const std::vector<char>& code, VkShaderModule *shaderModule)
    {
        VkShaderModuleCreateInfo info{};
        info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
        info.codeSize = code.size();
        info.pCode = reinterpret_cast<const uint32_t*>(code.data());

        if (vkCreateShaderModule(njmDevice.device(), &info, nullptr, shaderModule) != VK_SUCCESS)
        {
            std::cerr << "ERROR::PIPELINE::createShaderModule : failed to create shader-module" << std::endl;
            throw std::runtime_error("failde to create shader module");
        }       
    }

    void NjmPipeline::bind(VkCommandBuffer commandBuffer)
    {
        vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
    }

    void NjmPipeline::defaultPipelineConfigInfo(PipelineConfigInfo& pipelineInfo)
    {
        pipelineInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
        pipelineInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
        pipelineInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;

        pipelineInfo.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
        pipelineInfo.viewportInfo.viewportCount = 1;
        pipelineInfo.viewportInfo.pViewports = nullptr;
        pipelineInfo.viewportInfo.scissorCount = 1;
        pipelineInfo.viewportInfo.pScissors = nullptr;

        pipelineInfo.rasInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
        pipelineInfo.rasInfo.cullMode = VK_CULL_MODE_NONE;
        pipelineInfo.rasInfo.frontFace = VK_FRONT_FACE_CLOCKWISE;
        pipelineInfo.rasInfo.lineWidth = 1.0f;
        pipelineInfo.rasInfo.polygonMode = VK_POLYGON_MODE_FILL;
        pipelineInfo.rasInfo.rasterizerDiscardEnable = VK_FALSE;
        pipelineInfo.rasInfo.depthClampEnable = VK_FALSE;
        pipelineInfo.rasInfo.depthBiasEnable = VK_FALSE;
        pipelineInfo.rasInfo.depthBiasClamp = 0.0f;
        pipelineInfo.rasInfo.depthBiasConstantFactor = 0.0f;
        pipelineInfo.rasInfo.depthBiasSlopeFactor = 0.0f;

        pipelineInfo.msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
        pipelineInfo.msInfo.alphaToCoverageEnable = VK_FALSE;
        pipelineInfo.msInfo.alphaToOneEnable = VK_FALSE;
        pipelineInfo.msInfo.minSampleShading = 1.0f;
        pipelineInfo.msInfo.pSampleMask = nullptr;
        pipelineInfo.msInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;

        pipelineInfo.colorblendAttachment.blendEnable = VK_FALSE;
        pipelineInfo.colorblendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | 
            VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
        pipelineInfo.colorblendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
        pipelineInfo.colorblendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
        pipelineInfo.colorblendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
        pipelineInfo.colorblendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
        pipelineInfo.colorblendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
        pipelineInfo.colorblendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;

        pipelineInfo.colorblendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
        pipelineInfo.colorblendInfo.logicOpEnable = VK_FALSE;
        pipelineInfo.colorblendInfo.logicOp = VK_LOGIC_OP_COPY;
        pipelineInfo.colorblendInfo.attachmentCount = 1;
        pipelineInfo.colorblendInfo.pAttachments = &pipelineInfo.colorblendAttachment;
        pipelineInfo.colorblendInfo.blendConstants[0] = 0.0f;
        pipelineInfo.colorblendInfo.blendConstants[1] = 0.0f;
        pipelineInfo.colorblendInfo.blendConstants[2] = 0.0f;
        pipelineInfo.colorblendInfo.blendConstants[3] = 0.0f;

        pipelineInfo.depthstencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
        pipelineInfo.depthstencilInfo.depthTestEnable = VK_TRUE;
        pipelineInfo.depthstencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
        pipelineInfo.depthstencilInfo.depthWriteEnable = VK_TRUE;
        pipelineInfo.depthstencilInfo.depthBoundsTestEnable = VK_FALSE;
        pipelineInfo.depthstencilInfo.minDepthBounds = 0.0f;
        pipelineInfo.depthstencilInfo.maxDepthBounds = 1.0f;
        pipelineInfo.depthstencilInfo.stencilTestEnable = VK_FALSE;
        pipelineInfo.depthstencilInfo.front = {};
        pipelineInfo.depthstencilInfo.back = {};

        pipelineInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
        pipelineInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
        pipelineInfo.dynamicStateInfo.dynamicStateCount = static_cast<uint32_t>(pipelineInfo.dynamicStateEnables.size());
        pipelineInfo.dynamicStateInfo.pDynamicStates = pipelineInfo.dynamicStateEnables.data();
        pipelineInfo.dynamicStateInfo.flags = 0;

        pipelineInfo.bindingDescriptions = NjmModel::Vertex::getBindingDescriptions();
        pipelineInfo.attributeDescriptions = NjmModel::Vertex::getAttributeDescriptions();
    }

    void NjmPipeline::enableAlphaBlending(PipelineConfigInfo &pipelineInfo)
    {
        pipelineInfo.colorblendAttachment.blendEnable = VK_TRUE;
        pipelineInfo.colorblendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | 
            VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
        pipelineInfo.colorblendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
        pipelineInfo.colorblendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
        pipelineInfo.colorblendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
        pipelineInfo.colorblendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
        pipelineInfo.colorblendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
        pipelineInfo.colorblendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
    }

    void NjmPipeline::disableDepthWrite(PipelineConfigInfo &pipelineInfo)
    {
    }
}